﻿//IIndicatorValue.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Indicators
{
	using System;

	using Ecng.Common;

	using StockSharp.Algo.Candles;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// Входное значение индикатора, на основе которого он обновит свое значение, а так значение, хранящее результат вычисления индикатора.
	/// </summary>
	public interface IIndicatorValue
	{
		/// <summary>
		/// Значение индикатора отсутствует.
		/// </summary>
		bool IsEmpty { get; }

		/// <summary>
		/// Является ли значение окончательным (индикатор окончательно формирует свое значение и более не будет изменяться в данной точке времени).
		/// </summary>
		bool IsFinal { get; }

		/// <summary>
		/// Поддерживает ли значение необходимый для индикатора тип данных.
		/// </summary>
		/// <param name="valueType">Тип данных, которым оперирует индикатор.</param>
		/// <returns>True, если тип данных поддерживается, иначе, false.</returns>
		bool IsSupport(Type valueType);

		/// <summary>
		/// Получить значение по типу данных.
		/// </summary>
		/// <typeparam name="T">Тип данных, которым оперирует индикатор.</typeparam>
		/// <returns>Значение.</returns>
		T GetValue<T>();

		/// <summary>
		/// Изменить входное значение индикатора новым значением (например, оно получено от другого индикатора).
		/// </summary>
		/// <typeparam name="T">Тип данных, которым оперирует индикатор.</typeparam>
		/// <param name="value">Значение.</param>
		/// <returns>Новый объект, содержащий входное значение.</returns>
		IIndicatorValue SetValue<T>(T value);
	}

	/// <summary>
	/// Базовое значение индикатора, работающее с один типом данных.
	/// </summary>
	/// <typeparam name="TValue">Тип значения.</typeparam>
	public class SingleIndicatorValue<TValue> : IIndicatorValue
	{
		/// <summary>
		/// Создать <see cref="SingleIndicatorValue{TValue}"/>.
		/// </summary>
		/// <param name="value">Значение.</param>
		public SingleIndicatorValue(TValue value)
		{
			Value = value;
			IsEmpty = value.IsNull();
		}

		/// <summary>
		/// Создать <see cref="SingleIndicatorValue{TValue}"/>.
		/// </summary>
		public SingleIndicatorValue()
		{
			IsEmpty = true;
		}

		/// <summary>
		/// Значение.
		/// </summary>
		public TValue Value { get; private set; }

		/// <summary>
		/// Значение индикатора отсутствует.
		/// </summary>
		public bool IsEmpty { get; private set; }

		/// <summary>
		/// Является ли значение окончательным (индикатор окончательно формирует свое значение и более не будет изменяться в данной точке времени).
		/// </summary>
		public bool IsFinal { get; set; }

		/// <summary>
		/// Поддерживает ли значение необходимый для индикатора тип данных.
		/// </summary>
		/// <param name="valueType">Тип данных, которым оперирует индикатор.</param>
		/// <returns>True, если тип данных поддерживается, иначе, false.</returns>
		public virtual bool IsSupport(Type valueType)
		{
			return valueType == typeof(TValue);
		}

		/// <summary>
		/// Получить значение по типу данных.
		/// </summary>
		/// <typeparam name="T">Тип данных, которым оперирует индикатор.</typeparam>
		/// <returns>Значение.</returns>
		public virtual T GetValue<T>()
		{
			ThrowIfEmpty();
			return Value.To<T>();
		}

		/// <summary>
		/// Изменить входное значение индикатора новым значением (например, оно получено от другого индикатора).
		/// </summary>
		/// <typeparam name="T">Тип данных, которым оперирует индикатор.</typeparam>
		/// <param name="value">Значение.</param>
		/// <returns>Новый объект, содержащий входное значение.</returns>
		public virtual IIndicatorValue SetValue<T>(T value)
		{
			return new SingleIndicatorValue<T>(value);
		}

		private void ThrowIfEmpty()
		{
			if (IsEmpty)
				throw new InvalidOperationException("Значение индикатора не содержит данных.");
		}

		/// <summary>
		/// Получить строковое представление.
		/// </summary>
		/// <returns>Строковое представление.</returns>
		public override string ToString()
		{
			return IsEmpty ? "Empty" : Value.ToString();
		}
	}

	/// <summary>
	/// Значение индикатора, работающее с типом данных <see cref="decimal"/>.
	/// </summary>
	public class DecimalIndicatorValue : SingleIndicatorValue<decimal>
	{
		/// <summary>
		/// Создать <see cref="DecimalIndicatorValue"/>.
		/// </summary>
		/// <param name="value">Значение.</param>
		public DecimalIndicatorValue(decimal value)
			: base(value)
		{
		}

		/// <summary>
		/// Создать <see cref="DecimalIndicatorValue"/>. 
		/// </summary>
		public DecimalIndicatorValue()
		{
		}

		/// <summary>
		/// Привести <see cref="decimal"/> значение к объекту <see cref="DecimalIndicatorValue"/>.
		/// </summary>
		/// <param name="value"><see cref="decimal"/> значение.</param>
		/// <returns>Объект <see cref="DecimalIndicatorValue"/>.</returns>
		public static implicit operator DecimalIndicatorValue(decimal value)
		{
			return new DecimalIndicatorValue(value);
		}

		/// <summary>
		/// Изменить входное значение индикатора новым значением (например, оно получено от другого индикатора).
		/// </summary>
		/// <typeparam name="T">Тип данных, которым оперирует индикатор.</typeparam>
		/// <param name="value">Значение.</param>
		/// <returns>Новый объект, содержащий входное значение.</returns>
		public override IIndicatorValue SetValue<T>(T value)
		{
			return typeof(T) == typeof(decimal)
				? new DecimalIndicatorValue(value.To<decimal>())
				: base.SetValue(value);
		}
	}

	/// <summary>
	/// Значение индикатора, работающее с типом данных <see cref="Candle"/>.
	/// </summary>
	public class CandleIndicatorValue : SingleIndicatorValue<Candle>
	{
		private readonly Func<Candle, decimal> _getPart;

		/// <summary>
		/// Создать <see cref="CandleIndicatorValue"/>.
		/// </summary>
		/// <param name="value">Значение.</param>
		public CandleIndicatorValue(Candle value)
			: this(value, ByClose)
		{
		}

		/// <summary>
		/// Создать <see cref="CandleIndicatorValue"/>.
		/// </summary>
		/// <param name="value">Значение.</param>
		/// <param name="getPart">Конвертер свечки, через который можно получить ее параметр. По-умолчанию используется <see cref="ByClose"/>.</param>
		public CandleIndicatorValue(Candle value, Func<Candle, decimal> getPart)
			: base(value)
		{
			if (value == null)
				throw new ArgumentNullException("value");

			if (getPart == null)
				throw new ArgumentNullException("getPart");

			_getPart = getPart;

			IsFinal = value.State == CandleStates.Finished;
		}

		/// <summary>
		/// Создать <see cref="CandleIndicatorValue"/>.
		/// </summary>
		private CandleIndicatorValue()
		{
		}

		/// <summary>
		/// Конвертер, который берет из свечки цену закрытия <see cref="Candle.ClosePrice"/>.
		/// </summary>
		public static readonly Func<Candle, decimal> ByClose = c => c.ClosePrice;

		/// <summary>
		/// Конвертер, который берет из свечки цену открытия <see cref="Candle.OpenPrice"/>.
		/// </summary>
		public static readonly Func<Candle, decimal> ByOpen = c => c.OpenPrice;

		/// <summary>
		/// Конвертер, который берет из свечки середину тела (<see cref="Candle.OpenPrice"/> + <see cref="Candle.ClosePrice"/>) / 2.
		/// </summary>
		public static readonly Func<Candle, decimal> ByMiddle = c => (c.ClosePrice + c.OpenPrice) / 2;

		/// <summary>
		/// Привести <see cref="Candle"/> значение к объекту <see cref="CandleIndicatorValue"/>.
		/// </summary>
		/// <param name="value"><see cref="Candle"/> значение.</param>
		/// <returns>Объект <see cref="CandleIndicatorValue"/>.</returns>
		public static implicit operator CandleIndicatorValue(Candle value)
		{
			return new CandleIndicatorValue(value);
		}

		/// <summary>
		/// Поддерживает ли значение необходимый для индикатора тип данных.
		/// </summary>
		/// <param name="valueType">Тип данных, которым оперирует индикатор.</param>
		/// <returns>True, если тип данных поддерживается, иначе, false.</returns>
		public override bool IsSupport(Type valueType)
		{
			return valueType == typeof(decimal) || base.IsSupport(valueType);
		}

		/// <summary>
		/// Получить значение по типу данных.
		/// </summary>
		/// <typeparam name="T">Тип данных, которым оперирует индикатор.</typeparam>
		/// <returns>Значение.</returns>
		public override T GetValue<T>()
		{
			var candle = base.GetValue<Candle>();
			return typeof(T) == typeof(decimal) ? _getPart(candle).To<T>() : candle.To<T>();
		}

		/// <summary>
		/// Изменить входное значение индикатора новым значением (например, оно получено от другого индикатора).
		/// </summary>
		/// <typeparam name="T">Тип данных, которым оперирует индикатор.</typeparam>
		/// <param name="value">Значение.</param>
		/// <returns>Новый объект, содержащий входное значение.</returns>
		public override IIndicatorValue SetValue<T>(T value)
		{
			var candle = value as Candle;

			return candle != null
			       	? new CandleIndicatorValue(candle)
			       	: value.IsNull() ? new CandleIndicatorValue() : base.SetValue(value);
		}
	}

	/// <summary>
	/// Значение индикатора, работающее с типом данных <see cref="MarketDepth"/>.
	/// </summary>
	public class MarketDepthIndicatorValue : SingleIndicatorValue<MarketDepth>
	{
		private readonly Func<MarketDepth, decimal> _getPart;

		/// <summary>
		/// Создать <see cref="MarketDepthIndicatorValue"/>.
		/// </summary>
		/// <param name="depth">Стакан.</param>
		public MarketDepthIndicatorValue(MarketDepth depth)
			: this(depth, ByMiddle)
		{
		}

		/// <summary>
		/// Создать <see cref="MarketDepthIndicatorValue"/>.
		/// </summary>
		/// <param name="depth">Стакан.</param>
		/// <param name="getPart">Конвертер стакана, через который можно получить его параметр. По-умолчанию используется <see cref="ByMiddle"/>.</param>
		public MarketDepthIndicatorValue(MarketDepth depth, Func<MarketDepth, decimal> getPart)
			: base(depth)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			if (getPart == null)
				throw new ArgumentNullException("getPart");

			_getPart = getPart;
		}

		/// <summary>
		/// Конвертер, который берет из стакана цену лучшего бида <see cref="MarketDepth.BestBid"/>.
		/// </summary>
		public static readonly Func<MarketDepth, decimal> ByBestBid = d => d.BestBid != null ? d.BestBid.Price : 0;

		/// <summary>
		/// Конвертер, который берет из стакана цену лучшего оффера <see cref="MarketDepth.BestAsk"/>.
		/// </summary>
		public static readonly Func<MarketDepth, decimal> ByBestAsk = d => d.BestAsk != null ? d.BestAsk.Price : 0;

		/// <summary>
		/// Конвертер, который берет из стакана середину спреда <see cref="MarketDepthPair.MiddlePrice"/>.
		/// </summary>
		public static readonly Func<MarketDepth, decimal> ByMiddle = d => d.BestPair.MiddlePrice;

		/// <summary>
		/// Поддерживает ли значение необходимый для индикатора тип данных.
		/// </summary>
		/// <param name="valueType">Тип данных, которым оперирует индикатор.</param>
		/// <returns>True, если тип данных поддерживается, иначе, false.</returns>
		public override bool IsSupport(Type valueType)
		{
			return valueType == typeof(decimal) || base.IsSupport(valueType);
		}

		/// <summary>
		/// Получить значение по типу данных.
		/// </summary>
		/// <typeparam name="T">Тип данных, которым оперирует индикатор.</typeparam>
		/// <returns>Значение.</returns>
		public override T GetValue<T>()
		{
			var depth = base.GetValue<MarketDepth>();
			return typeof(T) == typeof(decimal) ? _getPart(depth).To<T>() : depth.To<T>();
		}

		/// <summary>
		/// Изменить входное значение индикатора новым значением (например, оно получено от другого индикатора).
		/// </summary>
		/// <typeparam name="T">Тип данных, которым оперирует индикатор.</typeparam>
		/// <param name="value">Значение.</param>
		/// <returns>Новый объект, содержащий входное значение.</returns>
		public override IIndicatorValue SetValue<T>(T value)
		{
			return new MarketDepthIndicatorValue(base.GetValue<MarketDepth>(), _getPart);
		}
	}
}